package p05_BeanFactory后处理器;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.stereotype.Component;

/**
 * BeanFactory后处理器ConfigurationClassPostProcessor是如何解析@ComponentScan注解的呢？
 * 这节模拟解析过程
 * 假设这里已经得到了Config类
 * CachingMetadataReaderFactory类读取类元信息：效率比反射高
 */
public class _2_解析ComponentScan注解 {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        /**
         * 往容器中添加我们自定义的beanFactory后处理器
         * 容器refresh时，会得到所有beanFactory后处理器类型的bean，然后一一运行他们(就是第二章的:postProcessor.postProcessBeanFactory(beanFactory))
         * 运行的本质就是调用后处理器的postProcessBeanFactory方法(beanFactory后处理器都实现了BeanFactoryPostProcessor,所以肯定有postProcessBeanFactory方法)
         */
        context.registerBean(MyComponentScan.class);
        context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }

    /**
     * 自定义beanFactory后处理器，来解析@ComponentScan
     */
    static class MyComponentScan implements BeanFactoryPostProcessor {
        //bean后处理器，加入容器后，执行context.refresh()就会回调这个方法
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            try {
                //下面两行代码将ConfigurableListableBeanFactory类型的BeanFactory变成GenericApplicationContext类型
                DefaultListableBeanFactory b = (DefaultListableBeanFactory) beanFactory;
                GenericApplicationContext context = new GenericApplicationContext(b);
                //这玩意负责读取类的元信息
                CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
                //这玩意负责给bean取名字
                AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
                /**
                 * 解析@ComponentScan思路：
                 *  1.判断某个类上是否有@ComponentScan注解
                 *  2.如果有，得到该注解的basePackages属性，将其转换为一个正确的路径字符串
                 *  3.根据路径字符串，获得对应路径下所有资源
                 *  4.通过CachingMetadataReaderFactory获取每一个资源的类元信息和注解信息，比如可以获取该类有没有添加注解
                 *  5.加了注解，则将其加入容器
                 * 这里假设已经提前得到了Config类，所以直接使用
                 */
                ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
                //不为空的话...
                if (componentScan != null) {
                    //要扫描的包路径可能有多个
                    for (String path : componentScan.basePackages()) {
                        //将a.b.c格式转化为:classpath*:a/b/c/**/*.class(通配符)
                        path = "classpath*:" + path.replace(".", "/") + "/**/*.class";

                        //根据上面得到的通配符找到资源(class文件)，下面两种根据通配符获取资源的代码等价
                        Resource[] resources = context.getResources(path);
                        resources = new PathMatchingResourcePatternResolver().getResources(path);

                        //循环每一个class文件，看该class是否可以被注册进容器
                        for (Resource resource : resources) {
                            //读取class元信息
                            MetadataReader reader = factory.getMetadataReader(resource);
                            //获取该类有没有加指定注解及其派生注解(hasAnnotation:判断是否有指定注解;hasMetaAnnotation:判断是否有指定注解及其派生注解)
                            AnnotationMetadata metadata = reader.getAnnotationMetadata();
                            if (metadata.hasAnnotation(Component.class.getName()) || metadata.hasMetaAnnotation(Component.class.getName())) {
                                System.out.println("扫描到了类:" + resource + ", 准备加入容器");

                                //生成该bean的bean定义对象
                                AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
                                        .genericBeanDefinition(reader.getClassMetadata().getClassName())
                                        .getBeanDefinition();
                                //根据bean定义生成该bean的名字
                                String beanName = generator.generateBeanName(beanDefinition, context.getDefaultListableBeanFactory());
                                //将bean定义注册进容器
                                context.registerBeanDefinition(beanName, beanDefinition);
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }


}
